﻿using System;
using System.Collections.Generic;
using System.Linq;
using OpenTK;
using OpenTK.Graphics.OpenGL;
using OpenTK.Graphics;
using System.Windows.Forms;


namespace AirHockey
{
    public class ComponentManager
    {
        #region Members
        private Camera camera;
        private GLControl glControl;
        private int shaderProgramHandle, vertexShaderHandle, fragmentShaderHandle;
        private int projectionMatrixLocation, viewMatrixLocation, modelMatrixLocation,lightPositionLocation,
                    shininessLocation,ambientProductLocation,diffuseProductLocation,specularProductLocation,texMapLocation;
        private Light light;
        private List<IDrawableComponent> drawComponents;
        private List<IUpdateableComponent> updateComponents;
        private Matrix4 projectionMatrix, modelMatrix, viewMatrix;
        #endregion

        #region Properties 
        public Camera Camera
        {
            get { return camera; }
            set { camera = value; }
        }

        public Light Light
        {
            get { return light; }
            set { light = value; }
        }
        #endregion

        #region Constructor
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="glControl">GL Control that component manager is responsible for updating</param>
        public ComponentManager(GLControl glControl)
        {
            this.glControl = glControl;
            drawComponents = new List<IDrawableComponent>();
            updateComponents = new List<IUpdateableComponent>();
            camera = new Camera(new Vector3(5f, 15f, 20f));
            light = new Light(new Color4(1f, 1f, 1f, 0f), new Color4(1f, 1f, 1f, 0f), new Color4(1f, 1f, 1f, 0f), new Vector3(0f, 100f, 0f));
            SetViewMatrix(Matrix4.LookAt(new Vector3(0.0f, 7.0f, 5.0f), Vector3.Zero, Vector3.UnitY));
            SetProjectionMatrix(Matrix4.CreateOrthographic(10f, 10f, 5f, -5f));
            CreateShaders();
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Adds a component to the drawable and/or updateable components list
        /// </summary>
        /// <param name="component">Component to add</param>
        public void Add(IComponent component)
        {
            component.Initialize();

            if(component is IDrawableComponent)
            {
                 drawComponents.Add((IDrawableComponent)component);
            }

            if (component is IUpdateableComponent)
            {
                updateComponents.Add((IUpdateableComponent)component);
            }
        }

        public void RotateCamera(float rotation)
        {

        }

        /// <summary>
        /// Changes the modelview uniform in the shader. 
        /// </summary>
        /// <param name="matrix">Modelview Matrix for the object</param>
        public void SetModelMatrix(Matrix4 matrix)
        {
            modelMatrix = matrix;
            GL.UniformMatrix4(modelMatrixLocation, false, ref modelMatrix);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="shininess"></param>
        private void SetModelShininess(float shininess)
        {
            GL.Uniform1(shininessLocation, shininess);
        }

        /// <summary>
        /// Changes the view uniform in the shader.
        /// </summary>
        /// <param name="matrix">View Matrix</param>
        public void SetViewMatrix(Matrix4 matrix)
        {
            viewMatrix = matrix;
            GL.UniformMatrix4(viewMatrixLocation, false, ref viewMatrix);
        }

        /// <summary>
        /// Changes the projection view uniform in the shader.
        /// </summary>
        /// <param name="matrix">The projection matrix</param>
        public void SetProjectionMatrix(Matrix4 matrix)
        {
            projectionMatrix = matrix;
            GL.UniformMatrix4(projectionMatrixLocation, false, ref projectionMatrix);
        }

        /// <summary>
        /// Changes the lighting calculations in the shader
        /// </summary>
        /// <param name="light">Light Settings</param>
        public void SetLighting(Material material,Light light)

        {
            //Set Uniform for Ambient Product
            Color4 calculatedAmbientProduct = new Color4(material.Ka.R * light.LightAmbient.R, material.Ka.G * light.LightAmbient.G,
                                                           material.Ka.B * light.LightAmbient.B, material.Ka.A * light.LightAmbient.A);

            GL.Uniform4(ambientProductLocation, calculatedAmbientProduct);

            //Set Uniform for Diffuse Product
            Color4 calculatedDiffuseProduct = new Color4(material.Kd.R * light.LightDiffuse.R, material.Kd.G * light.LightDiffuse.G,
                                                           material.Kd.B * light.LightDiffuse.B, material.Kd.A * light.LightDiffuse.A);

            GL.Uniform4(diffuseProductLocation, calculatedDiffuseProduct);

            //Set Uniform for Specular Product
            Color4 calculatedSpecularProduct = new Color4(material.Ks.R * light.LightSpecular.R, material.Ks.G * light.LightSpecular.G,
                                                           material.Ks.B * light.LightSpecular.B, material.Ks.A * light.LightSpecular.A);

            GL.Uniform4(specularProductLocation, calculatedSpecularProduct);

            //Set Uniform for Light Position
            GL.Uniform4(lightPositionLocation, new Vector4(light.LightPosition,1));
            GL.Uniform1(shininessLocation, 200);
        }

        /// <summary>
        /// Removes a component from the drawable and/or updateable components list
        /// </summary>
        /// <param name="component">Component to remove</param>
        public void Remove(IComponent component)
        {
            if (component is IDrawableComponent)
            {
                drawComponents.Remove((IDrawableComponent)component);
            }

            if (component is IUpdateableComponent)
            {
                updateComponents.Remove((IUpdateableComponent)component);
            }
        }

        /// <summary>
        /// Executes update logic and redraws each component
        /// </summary>
        /// <param name="gameTime"></param>
        public void Update(GameTime gameTime)
        {
            GL.Clear(ClearBufferMask.ColorBufferBit|ClearBufferMask.DepthBufferBit);
            GL.ClearColor(Color4.Black);
            glControl.Update();

            foreach (IUpdateableComponent component in updateComponents)
            {
                if (component.Enabled)
                {
                    component.Update();
                }
            }

            foreach (IDrawableComponent component in drawComponents)
            {
                if (component.Visible)
                {
                    //set model matrix component
                    SetModelMatrix(component.ModelView);
                    //set lighting
                    SetLighting(component.Material, light);
                    component.Draw();
                }
            }

            glControl.SwapBuffers();
        }
        #endregion

        #region Private Methods
        /// <summary>
        /// Creates and binds the shaders.  Also is responsible for binding the uniform variables
        /// </summary>
        private void CreateShaders()
        {
            #region VertShaderColor
            string vertSourceColor = @"
            #version 150
            uniform mat4 projectionMatrix;
            uniform mat4 viewMatrix;  
            uniform mat4 modelMatrix;
            uniform vec4 lightPosition;
          
            in vec4 vertexPosition;
            in vec3 vertexNormal;
            in vec2 vertexUv;

            out vec3 N;
            out vec3 L;
            out vec3 E;
            out vec2 UV; 

            void main()
            {
	            gl_Position = projectionMatrix*viewMatrix*modelMatrix*vertexPosition;
                N = vertexNormal.xyz;
                L = lightPosition.xyz-vertexPosition.xyz;
                
                if(lightPosition.w == 0) 
                {
                    L = lightPosition.xyz;
                }

                E = vertexPosition.xyz;
                UV = vertexUv;
            }";
            #endregion

            #region FragSourceColor
            string fragSourceColor=@"
            #version 150
            uniform vec4 ambientProduct,diffuseProduct,specularProduct;
            uniform float shininess;
            uniform sampler2D texMap;
            
            in vec3 N;
            in vec3 L; 
            in vec3 E;
            in vec2 UV;

            out vec4 fColor;
            
            void main()
            {
                vec3 nn = normalize(N);
                vec3 ee = normalize(E);
                vec3 ll = normalize(L);

                vec3 h = normalize(ll+ee);

                vec4 ambient = ambientProduct;
                
                float kD = max(dot(ll,ee),0.0);
                vec4 diffuse = kD*diffuseProduct;
           
                float kS = pow(max(dot(nn,h),0.0),shininess);    
                vec4 specular = kS*specularProduct;

                if(dot(ll,nn) < 0.0)
                {
                   specular = vec4(1.0,1.0,1.0,0.0); 
                }
                
                fColor = vec4((ambient + diffuse + specular).xyz, 0.0) * texture2D(texMap,vec2(UV.x, UV.y ) );
            }";
            #endregion

            #region VertShaderTexture
            string vertSource = @"
            #version 150
            uniform mat4 projectionMatrix;
            uniform mat4 viewMatrix;  
            uniform mat4 modelMatrix;
            uniform vec4 lightPosition;
          
            in vec4 vertexPosition;
            in vec3 vertexNormal;
            in vec2 vertexUv;

            out vec3 N;
            out vec3 L;
            out vec3 E;
            out vec2 UV; 

            void main()
            {
	            gl_Position = projectionMatrix*viewMatrix*modelMatrix*vertexPosition;
                N = vertexNormal.xyz;
                L = lightPosition.xyz-vertexPosition.xyz;
                
                if(lightPosition.w == 0) 
                {
                    L = lightPosition.xyz;
                }

                E = vertexPosition.xyz;
                UV = vertexUv;
            }";
            #endregion

            #region FragSourceTexture
            string fragSource = @"
            #version 150
            uniform vec4 ambientProduct,diffuseProduct,specularProduct;
            uniform float shininess;
            uniform sampler2D texMap;
            
            in vec3 N;
            in vec3 L; 
            in vec3 E;
            in vec2 UV;

            out vec4 fColor;
            
            void main()
            {
                vec3 nn = normalize(N);
                vec3 ee = normalize(E);
                vec3 ll = normalize(L);

                vec3 h = normalize(ll+ee);

                vec4 ambient = ambientProduct;
                
                float kD = max(dot(ll,ee),0.0);
                vec4 diffuse = kD*diffuseProduct;
           
                float kS = pow(max(dot(nn,h),0.0),shininess);    
                vec4 specular = kS*specularProduct;

                if(dot(ll,nn) < 0.0)
                {
                   specular = vec4(1.0,1.0,1.0,0.0); 
                }
                
                fColor = vec4((ambient + diffuse + specular).xyz, 0.0) * texture2D(texMap,vec2(UV.x, UV.y ) );
            }";
            #endregion

            //Creates handles for program,vertex and fragment shaders
            shaderProgramHandle = GL.CreateProgram();
            vertexShaderHandle = GL.CreateShader(ShaderType.VertexShader);
            fragmentShaderHandle = GL.CreateShader(ShaderType.FragmentShader);
            //Bind shader programs
            GL.ShaderSource(vertexShaderHandle, vertSource);
            GL.ShaderSource(fragmentShaderHandle, fragSource);
            //Compile shaders 
            GL.CompileShader(vertexShaderHandle);
            GL.CompileShader(fragmentShaderHandle);
            Console.WriteLine(GL.GetShaderInfoLog(vertexShaderHandle));
            Console.WriteLine(GL.GetShaderInfoLog(fragmentShaderHandle));
            //Link Shaders
            GL.AttachShader(shaderProgramHandle, vertexShaderHandle);
            GL.AttachShader(shaderProgramHandle, fragmentShaderHandle);
            GL.LinkProgram(shaderProgramHandle);
            Console.WriteLine(GL.GetProgramInfoLog(shaderProgramHandle));
            //Bind vertex attribute
            GL.BindAttribLocation(shaderProgramHandle, 0, "vertexPosition");
            GL.BindAttribLocation(shaderProgramHandle, 1, "vertexNormal");
            GL.BindAttribLocation(shaderProgramHandle, 2, "vertexUv");
            //Set up uniforms
            projectionMatrixLocation = GL.GetUniformLocation(shaderProgramHandle, "projectionMatrix");
            viewMatrixLocation = GL.GetUniformLocation(shaderProgramHandle, "viewMatrix");
            modelMatrixLocation = GL.GetUniformLocation(shaderProgramHandle, "modelMatrix");
            lightPositionLocation = GL.GetUniformLocation(shaderProgramHandle, "lightPosition");
            shininessLocation = GL.GetUniformLocation(shaderProgramHandle, "shininess");
            ambientProductLocation = GL.GetUniformLocation(shaderProgramHandle, "ambientProduct");
            diffuseProductLocation = GL.GetUniformLocation(shaderProgramHandle, "diffuseProduct");
            specularProductLocation = GL.GetUniformLocation(shaderProgramHandle, "specularProduct");
            texMapLocation = GL.GetUniformLocation(shaderProgramHandle, "texMap"); 
            //Start shader program
            GL.UseProgram(shaderProgramHandle);
            GL.Uniform1(GL.GetUniformLocation(texMapLocation, "texMap"),0);
        }

        #endregion
    }
}
